//==============================================================================
//
//         DPropertyTree - the property tree class in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2005-02-05 06:25:38 $ $Revision: 1.7 $
//
//==============================================================================

#include "ofc/DPropertyTree.h"

#include "ofc/DTree.h"


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DText.h"

#include "ofc/DParsable.h"
#include "ofc/DTextable.h"

#include "ofc/DTextReadable.h"
#include "ofc/DTextWritable.h"

//
// The DProperty class implements methods for using properties.
// 

@interface DProperty : Object
{
@private
   DText                    *_name;   // the group or property name
   id <DTextable,DParsable>  _value;  // the reference to the value 
}

#endif


@implementation DProperty


//// Constructors

//
// Initialise an empty property
//
// @return the object
//

- (DProperty *) init
{
  [super init];

  _name    = nil;
  _value   = nil;
  
  return self;
}

//
// Initialise a property
// 
// @param name         the property name
// @param value        the reference to the value object
// 
// @return the object
// 

- (DProperty *) init :(const char *) name :(id <DTextable,DParsable>) value
{
  [self init];
  
  [self property :name :value];
  
  return self;
}

//
// Initialise a group of properties
// 
// @param name         the group name
// 
// @return the object
// 

- (DProperty *) init :(const char *) name
{
  [self init];
  
  [self group :name];

  return self;
}


//// Copy related methods

//
// Deepen a copy of the property
//
// @return the object
//

- deepen
{
  [super deepen];

  if (_name != nil)
    _name = [_name copy];

  return self;
}


//// Destructor

//
// Free the property
//
// @return the object
//

- free
{
  if (_name != nil)
    [_name free];
  
  _value = nil;
  
  return [super free];
}


//// Member methods

//
// Check if the property is a group 
// 
// @return is it ?
//
- (BOOL) isGroup
{
  return (_value == nil);
}

//
// Return the name (of the property or group)
// 
// @return the property name (or NULL)
// 

- (const char *) name
{
  return (_name != nil) ? [_name cstring] : NULL;
}

//
// Return the reference to the value object
// 
// @return the reference (or nil)
// 

- (id <DTextable,DParsable>) value
{
  return _value;
}


//// Main methods

//
// Set the property name and reference object for the property
// 
// @param name         the property name
// @param value        the reference object
//
// @return the object
//

- (DProperty *) property :(const char *) name :(id <DTextable,DParsable>) value
{
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if (value == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "value");
  }
  else
  {
    if (_name == nil)
      _name = [DText new];
    
    [_name set :name];
    
    _value = value;
  }
  
  return self;
}

//
// Set the group name for a property group
//  
// @param name      the name of the group
//
// @return the object
//

- (DProperty *) group :(const char *) name
{
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else
  {
    if (_name == nil)
      _name = [DText new];
    
    [_name set :name];
    
    _value = nil;
  }
  
  return self;
}
    
@end



  
 
#if _INTERFACE_

#include "ofc/DXML.h"

#ifdef HAVE_DXML
#define HAVE_DPROPERTYTREE
#endif
  
#ifdef HAVE_DPROPERTYTREE

//
// The DPropertyTree class implements methods for storing and retrieving
// properties in a xml file. There are methods for adding, removing, importing 
// and exporting properties. 
// 

@interface DPropertyTree : Object <DXMLHandler>
{
@private
  DTree                    *_properties; // the property tree
  DTreeIterator            *_iter;       // the property tree iterator
  DProperty                *_root;       // the root of the properties
  DXMLReader               *_reader;     // the xml reader
}

#endif
#endif


#ifdef HAVE_DPROPERTYTREE

@implementation DPropertyTree


#define _INDENT  (2)

//// Constructors

//
// Initialise an empty property tree
//
// @return the object
//

- (DPropertyTree *) init
{
  [super init];

  _root       = [DProperty alloc];
  
  [_root init :"Properties"];
  
  _properties = [DTree new];
  _iter       = [DTreeIterator alloc];
  
  [_iter init :_properties];
  
  [_iter root];
  
  [_iter append :_root];

  _reader = nil;
  
  return self;
}

//
// Initialise a named property tree
//
// @param name      the name of the property tree
// 
// @return the object
//
- (DPropertyTree *) init :(const char *) name
{
  [self init];
  
  [self name :name];
  
  return self;
}

//// Copy related methods

//
// Deepen a copy of the property tree (not implemented)
//
// @return the object
//

- deepen
{
  [super deepen];

  WARNING(DW_METHOD_NOT_IMPL, "deepen");
  
  return self;
}


//// Destructor

//
// Free the property tree
//
// @return the object
//

- free
{
  [_iter       free]; _iter       = nil;
  [_properties free]; _properties = nil;
  
  if (_reader != nil)
  {
    [_reader free]; _reader = nil;
  }
  
  return [super free];
}


//// Member methods

//
// Get the name of the tree
// 
// @return the name of the tree
// 
- (const char *) name
{
  return [_root name];
}

//
// Set the name of the tree
// 
// @param name      the name of the tree
// 
// @return the object
// 
- (DPropertyTree *) name :(const char *) name
{
  [_root group :name];
  
  return self;
}


//// Main methods

//
// Add a group to the property tree
//
// @param parent   the parent (group) property 
// @param name     the group name
//
// @return the (new) group property (or nil)
//

- (DProperty *) group :(DProperty *) parent :(const char *) name
{
  DProperty *group = nil;
  
  if ((parent == nil) || ([parent isGroup]))
  {
    group = [DProperty new];
  
    [group group :name];

    if (![self _addProperty :parent :group])
    {
      [group free]; group = nil;
    }
  }
  else
  {
    WARNING(DW_INVALID_ARG, "parent");
  }
  
  return group;
}

//
// Add a property to the tree of properties
// 
// @param parent   the parent (group) property (or nil for root)
// @param name     the property name
// @param value    the reference to the value object
// 
// @return the (new) property (or nil if parent not found)
// 

- (DProperty *) property :(DProperty *)              parent 
                         :(const char *)             name
                         :(id <DTextable,DParsable>) value
{
  DProperty *property = nil;
  
  if ((parent == nil) || ([parent isGroup]))
  {
    property = [DProperty new];
  
    [property property :name :value];
  
    if (![self _addProperty :parent :property])
    {
      [property free]; property = nil;
    }
  }
  else
  {
    WARNING(DW_INVALID_ARG, "parent");
  }
  
  return property;
}

//
// Import the properties
// 
// @param source   the properties source
// @param name     the name of the source
// 
// @return success
// 

- (BOOL) read :(id <DTextReadable>) source :(const char *) name
{
  BOOL ok = NO;
  
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if (source == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "source");
  }
  else
  {
    _reader = [DXMLReader new];
    
    ok = [_reader parse :source :name :self :EOS];
    
    [_reader free]; _reader = nil;
  }
  
  return ok;
}
    
//
// Export the properties
//
// @param destination the destination for the property tree 
// @param name        the name of the destination
// 
// @return success
// 

- (BOOL) write :(id <DTextWritable>) destination :(const char *) name
{
  BOOL ok = NO;

  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if (destination == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "destination");
  }
  else 
  {
    if (_properties != nil)
    {
      DXMLWriter *writer = [DXMLWriter new];
      
      if ([writer start :destination :EOS])
      {
        DTreeIterator *iter     = [DTreeIterator alloc];
        DProperty     *property = nil;
        int            level    = 0;
        DText         *indent   = [DText new];
        
        [iter init :_properties];
          
        property = [iter root];
        
        ok = [writer startDocument :"1.0" :NULL :-1];
        ok &= [writer characters :"\n"];
        
        while ((property != nil) && (ok))
        {
          [indent set :' ' :level];
          ok &= [writer characters :[indent cstring]];
          level += _INDENT;
          
          ok &= [writer startElement :[property name]];
            
          if ([property isGroup])
          {
            ok &= [writer characters :"\n"];
          }
          else
          {
            DText *value = [[property value] toText];
              
            ok &= [writer characters :[value cstring]];
          }
            
          if ([iter hasChildren])
          {
            property = [iter child];
          }
          else
          {
            do
            {
              level -= _INDENT;
              if ([property isGroup])
              {
                [indent set :' ' :level];
                ok &= [writer characters :[indent cstring]];
              }
              
              ok &= [writer endElement];
                
              ok &= [writer characters :"\n"];
                
              property = [iter next];
                
              if (property != nil) break;
                
              property = [iter parent];
            }
            while (property != nil);
          }
        }
          
        [writer endDocument];
        
        [indent free];  
        [iter   free];
      }
        
      [writer free];
    }
  }
    
  return ok;
}

//
// Remove a property from the property tree
// 
// @param property   the property to be removed (and FREED)
// 
// @return success (property is only freed if succesfull)
// 

- (BOOL) remove :(DProperty *) property
{
  BOOL ok = YES;
  
  if (property != nil)
  {
    if ([self _removeProperty :property])
    {
      [property free];
    }
    else
    {
      ok = NO;
    }
  }
  
  return ok;
}

//
// Remove a property from the property tree
// 
// @param parent   the parent (group) property
// @param name     the name of the property
// 
// @return success (property is freed if succesfull)
// 

- (BOOL) remove :(DProperty *) parent :(const char *) name
{
  BOOL ok = NO;
  
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else
  {
    DProperty *property = [self _findProperty :parent :name];
    
    if (property != nil)
    {
      ok = [DProperty remove :property];
    }
  }
  
  return ok;
}


//// Property methods (private methods)

//
// Add an property/group to the tree of properties
// 
// @param parent    the parent property
// @param property  the property to be added
// 
// @return is the parent property found ?
// 

- (BOOL) _addProperty :(DProperty *) parent :(DProperty *) property
{
  BOOL ok = YES;

  DTreeIterator *iter = [DTreeIterator alloc];
  
  if (_properties == nil)
  {
    DProperty *root = [DProperty new];
    
    _properties = [DTree new];
    
    [iter init :_properties];
    
    [iter root];
    
    [root group :"Properties"];
    
    [iter append :root];
  }
  else
  {
    [iter init :_properties];
  }

  if (parent == nil)
  {
    [iter root];
    
    [iter append :property];
  }
  else
  {
    if ([iter move :parent])
    {
      [iter append :property];
    }
    else
    {
      ok = NO;
    }
  }
  
  return ok;
}

//
// Find a property
// 
// @param parent   the group of the property
// @param name     the name of the property
// 
// @return the property (or nil if not found)
// 
- (DProperty *) _findProperty :(DProperty *) parent :(const char *) name
{
  DProperty *property = nil;
  
  if ((_properties != nil) && (name != NULL) && (*name != EOS))
  {
    DTreeIterator *iter = [DTreeIterator alloc];
    
    [iter init :_properties];
    
    if (parent == nil)
    {
      property = [iter root];
    }
    else
    {
      if ([iter move :parent])
      {
        property = [iter child];
      }
    }
    
    while (property != nil)
    {
      DText *txt = [iter object];
      
      if ([txt ccompare :name] == 0)
      {
        break;
      }
      
      property = [iter next];
    }
  }
  
  return property;
}

//
// Remove a property
// 
// @param property  the property to be removed
// 
// @return is the property removed ?
// 
- (BOOL) _removeProperty :(DProperty *) property
{
  BOOL ok = NO;
  
  if (_properties != nil)
  {
    DTreeIterator *iter = [DTreeIterator alloc];
    
    [iter init :_properties];
    
    if ([iter move :property])
    {
      ok = ([iter remove] != nil);
    }
  }
  
  return ok;
}

//// DXMLHandler protocol implementation (private methods)

//
// Process the start of a document
// 
// @param version     the version number
// @param encoding    the encoding
// @param standalone  is the xml document standalone ? (-1 = missing)
// 
// @return success
// 
- (BOOL) startDocument :(const DXMLChar *) version :(const DXMLChar *) encoding :(int) standalone
{
  [_iter root];

  return YES;
}

//
// Process the end of a document
// 
// @return success
// 
- (BOOL) endDocument
{
  [_iter root];

  return YES;
}

//
// Process the start of an element
// 
// @param name     the name of the element
// 
// @return success
// 
- (BOOL) startElement :(const DXMLChar *) name
{
  BOOL found = NO;
  
  if ([_iter isRoot])
  {
    DProperty *property = [_iter object];
    
    if ((property != nil) && (strcasecmp([property name], name) == 0))
    {
      found = YES;
    }
  }
  
  if (!found)
  {
    DProperty *property = [_iter child];
  
    while ((property != nil) && (strcasecmp([property name], name) != 0))
    {
      property = [_iter next];
    }
    
    if (property != nil)
    {
      found = YES;
    }
    else
    {
      [_iter parent];
    }
  }

  if (!found)
  {
    fprintf(stderr, "%s, line %d.%d: unknown property %s\n", 
            [_reader name], [_reader lineNumber], [_reader columnNumber], name);
    
    [_iter parent];
  }
  
  return YES;
}

//
// Process an attribute for an element (after a startElement)
// 
// @param attribute  the attribute
// @param value      the value for the attribute
// 
// @return success
// 
- (BOOL) attribute :(const DXMLChar *) attribute :(const DXMLChar *) value
{
  fprintf(stderr, "%s, line %d.%d: unsupported attribute %s\n", 
       [_reader name], [_reader lineNumber], [_reader columnNumber], attribute);
  
  return YES;
}

//
// Process the end of an element
// 
// @return success
//
- (BOOL) endElement
{
  if(![_iter isRoot])
  {
    [_iter parent];
  }
  return YES;
}

//
// Process a string of characters
// 
// @param text     the text of the characters
// 
// @return success
// 
- (BOOL) characters :(const DXMLChar *) text
{
  DProperty *property = [_iter object];
  
  if ((property != nil) && (![property isGroup]))
  {
    int result = -1;
    
    id <DParsable> value = [property value];
    
    // parse the text
    if (value != nil)
    {
      char *pntr = (DXMLChar *) text;
      
      result = [value fromString :&pntr];
    }
  }

  return YES;
}

//
// Process a comment
// 
// @param text     the text of the comment
// 
// @return success
// 
- (BOOL) comment :(const DXMLChar *) text
{
  // Ignore..
 
  return YES;
}

//
// Process a processing instruction
// 
// @param target   the target
// @param value    the value
// 
// @return success
// 
- (BOOL) processingInstruction :(const DXMLChar *) target :(const DXMLChar *) value
{
  // Ignore..
 
  return YES;
}

//
// Process the start of a CDATA section
// 
// @return success
// 
- (BOOL) startCDATA
{
  // ToDo: ..

  return YES;
}

//
// Process the end of a CDATA section
// 
// @return success
// 
- (BOOL) endCDATA
{
  // ToDo: ..

  return YES;
}

//
// Start of a namespace declaration
// 
// @param prefix   the prefix
// @param uri      the uri
// 
// @return success
// 
- (BOOL) startNamespace :(const DXMLChar *) prefix :(const DXMLChar *) uri
{
  fprintf(stderr, "%s, line %d.%d: unsupported namespace %s\n", 
          [_reader name], [_reader lineNumber], [_reader columnNumber], prefix);
 
  return YES;
}

//
// End of a namespace declaration
// 
// @return success
// 
- (BOOL) endNamespace
{
  // No namespaces in property file

  return YES;
}

//
// Process unparsed text in the xml document (e.g. the DTD)
// 
// @param text     the unparsed text
// 
// @return success
// 
- (BOOL) unparsed :(const DXMLChar *) text
{
  // Ignore..

  return YES;
}

// 
// An error is found during the parsing of the file
// 
// @param number       the error number
// @param name         the name of the config file
// @param lineNumber   the line number in the config file
// @param columnNumber the column number in the config file
// 
// @return success
// 
- (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber
{
  fprintf(stderr, "%s, line %d.%d: %s\n", name, lineNumber, columnNumber, [DXMLReader errorToString :number]);
}

@end

#endif

/*==========================================================================*/
